package worker

import (
	"crontab/common"
	"time"
)

//任务队列
type Scheduler struct {
	jobEventChan      chan *common.JobEvent
	jobSchedulerTasks map[string]*common.JobScheduleTask //每个任务都有自己的计划/调度表表
	jobRunningTasks   map[string]*common.JobExecuteInfo  //任务执行表
	jobResultChan     chan *common.JobExecutorResult     //任务结果队列
}

var (
	G_Scheduler *Scheduler
)

//针对不同的事件类型来处理进行不同操作
func (scheduler *Scheduler) addJobEvent(jobEvent *common.JobEvent) {
	var (
		jobSchedulerTask *common.JobScheduleTask
		jobExecuteInfo   *common.JobExecuteInfo
		jobRunning       bool
		jobExisted       bool
		err              error
	)
	switch jobEvent.EventType {
	case common.JOB_EVENT_SAVE:
		if jobSchedulerTask, err = common.BuildSchedulerTask(jobEvent.Job); err != nil {
			return
		}
		scheduler.jobSchedulerTasks[jobEvent.Job.Name] = jobSchedulerTask
	case common.JOB_EVENT_DELETE:
		if jobSchedulerTask, jobExisted = scheduler.jobSchedulerTasks[jobEvent.Job.Name]; jobExisted {
			delete(scheduler.jobSchedulerTasks, jobEvent.Job.Name)
		}
	case common.JOB_EVENT_KILLER: //取消任务,需要先判断是否正在运行
		if jobExecuteInfo, jobRunning = scheduler.jobRunningTasks[jobEvent.Job.Name]; jobRunning {
			jobExecuteInfo.CancelFunc() //触发command杀死shell子进程，任务会被取消
			if jobSchedulerTask, jobExisted = scheduler.jobSchedulerTasks[jobEvent.Job.Name]; jobExisted {
				delete(scheduler.jobSchedulerTasks, jobEvent.Job.Name)
			}
		}
	}
}

//重新计算任务调度状态
func (scheduler *Scheduler) ReCalcSchedulerTask() (schedulerAfter time.Duration) {
	var (
		jobSchedulertask *common.JobScheduleTask
		now              time.Time
		near             *time.Time
	)
	//如果任务表为空
	if len(scheduler.jobSchedulerTasks) == 0 {
		schedulerAfter = time.Second
		return
	}

	now = time.Now()
	//1.遍历所有任务，过期的任务立即执行，统计最近要过期的任务(假设N秒后过期)
	for _, jobSchedulertask = range scheduler.jobSchedulerTasks {
		if jobSchedulertask.NextTime.Before(now) || jobSchedulertask.NextTime.Equal(now) {
			//尝试执行任务，可能前一次没执行完，此时不会在继续执行
			scheduler.TryExecSchedulerTask(jobSchedulertask)
			jobSchedulertask.NextTime = jobSchedulertask.Expr.Next(now)
		}
		//统计最近一个要过期的任务事件
		if nil == near || jobSchedulertask.NextTime.Before(*near) {
			near = &jobSchedulertask.NextTime
		}
	}
	//下次调度间隔时间
	schedulerAfter = near.Sub(now)
	return
}

//开始调度协程
func (scheduler *Scheduler) ScheduleLoop() {
	var (
		jobEvent          *common.JobEvent
		schedulerAfter    time.Duration
		schedulerTimer    *time.Timer
		jobExectuorResult *common.JobExecutorResult
	)

	//初始化一次,如果为空那么就是1s
	schedulerAfter = scheduler.ReCalcSchedulerTask()

	//调度延迟器
	schedulerTimer = time.NewTimer(schedulerAfter)

	//启动一个定时任务
	for {
		select {
		case jobEvent = <-scheduler.jobEventChan: //监听任务变化的事件
			//对我们的列表做增删改查
			scheduler.addJobEvent(jobEvent)
		case <-schedulerTimer.C: //任务到时间点了，要被唤醒了
		case jobExectuorResult = <-scheduler.jobResultChan: //回收结果
			scheduler.processExecutorJobResult(jobExectuorResult)
		}
		schedulerAfter = scheduler.ReCalcSchedulerTask()
		//重置一下定时器
		schedulerTimer.Reset(schedulerAfter)
	}
}

//推送任务事件
func (scheduler *Scheduler) SendJobEventToScheduler(event *common.JobEvent) {
	scheduler.jobEventChan <- event
}

//尝试执行任务
func (scheduler *Scheduler) TryExecSchedulerTask(task *common.JobScheduleTask) {
	//调度和执行是2件事情，执行的任务可能会很久很，但是只能执行一次。所以我们需要记录这个状态.而且需要防止并发
	var (
		jobExecuteInfo *common.JobExecuteInfo

		jobExecuting bool
	)
	//如果已经在执行了，那么我们就直接返回
	if jobExecuteInfo, jobExecuting = scheduler.jobRunningTasks[task.Job.Name]; jobExecuting {
		//fmt.Println("任务尚未退出", jobExecuteInfo.Job.Name)
		return
	}
	//构建执行状态
	jobExecuteInfo = common.BuildSchedulerInfo(task)
	//保存执行状态
	scheduler.jobRunningTasks[task.Job.Name] = jobExecuteInfo

	//执行任务，需要并发调度
	G_Executor.ExecuteJob(jobExecuteInfo)

}

//向他调度器投递
func (scheduler *Scheduler) SendJobExecutorResult(jobResult *common.JobExecutorResult) {
	scheduler.jobResultChan <- jobResult
}

//处理调度的结果
func (scheduler *Scheduler) processExecutorJobResult(result *common.JobExecutorResult) {
	var (
		jobLog *common.JobLog
	)
	//删除执行的任务
	delete(scheduler.jobRunningTasks, result.Executor.Job.Name)
	if result.Err != common.ERR_LOCK_ALREAD_REQIURED {
		jobLog = &common.JobLog{
			JobName:      result.Executor.Job.Name,
			Command:      result.Executor.Job.Command,
			Output:       string(result.Ouput),
			PlanTime:     result.Executor.PlanTime.UnixNano() / 1000 / 1000, //毫秒就行
			ScheduleTime: result.Executor.RealTime.UnixNano() / 1000 / 1000,
			StartTime:    result.StartTime.UnixNano() / 1000 / 1000,
			EndTime:      result.EndTime.UnixNano() / 1000 / 1000,
			Author:       "夏雪飞",
		}
		if result.Err != nil {
			jobLog.Err = result.Err.Error()
		} else {
			jobLog.Err = ""
		}
		//我们不能在这里插入mogondb，因为这里是主循环，不能阻塞主循环
		G_LogPersist.AppendLog(jobLog)
	}

}

//创建一个1000的容量的协程
func InitScheduler() (err error) {
	G_Scheduler = &Scheduler{
		jobEventChan:      make(chan *common.JobEvent, 1000),
		jobSchedulerTasks: make(map[string]*common.JobScheduleTask),
		jobRunningTasks:   make(map[string]*common.JobExecuteInfo),
		jobResultChan:     make(chan *common.JobExecutorResult, 1000), //结果队列
	}
	//启动调度协程
	go G_Scheduler.ScheduleLoop()
	return
}
